package hku.cs.lunuo.rank.model.impl;

import hku.cs.lunuo.Global;
import hku.cs.lunuo.rank.StatisticProperty;
import hku.cs.lunuo.rank.StmtResult;
import hku.cs.lunuo.rank.StmtStatistic;
import hku.cs.lunuo.rank.model.Model;
import hku.cs.lunuo.rank.model.statistic.io.BlkStatisticProvider;
import hku.cs.lunuo.rank.model.statistic.io.StatisticProvider;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

import soot.Body;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Unit;
import soot.options.Options;
import soot.tagkit.LineNumberTag;
import soot.toolkits.graph.Block;
import soot.toolkits.graph.BlockGraph;
import soot.toolkits.graph.ExceptionalBlockGraph;
import soot.util.Chain;

public abstract class BlkBasingModel implements Model {

	BlkStatistic blkStatistic = new BlkStatistic();
	StmtStatistic stmtStatistic = new StmtStatistic();
	StmtResult stmtResult = new StmtResult();
	
	@Override
	public void analyze() {
		doAnalyze(blkStatistic);
		//blkStatistic.toSortedXML(new File(Global.v().toDoDir, Global.v().modelName+"blk.xml"));
	}
	
	protected abstract void doAnalyze(BlkStatistic blkStatistic);
	
	@Override
	public void toStmt() {
		Scene.v().loadBasicClasses();
		Options.v().set_keep_line_number(true);
		Options.v().setPhaseOption("jb", "use-original-names:true");
		
		for (String className : blkStatistic.getKlassNames()) {
			SootClass sClass = Scene.v().loadClassAndSupport(className);
			for (String methodName : blkStatistic.getMethodNames(className)) {
				
				SootMethod m = sClass.getMethod(methodName);
				if (m.isAbstract())
					continue;
				Body b = m.retrieveActiveBody();
				// it will error when the class is abstract
				BlockGraph bGraph = new ExceptionalBlockGraph(b);
				Chain<Unit> units = b.getUnits();
		
				for(Integer idx : blkStatistic.getBlks(className, methodName)) {	
					Block block = bGraph.getBlocks().get(idx);
					Iterator it = units.iterator(block.getHead(), block.getTail());
					while(it.hasNext()) {
						Unit u = (Unit)it.next();
						LineNumberTag lng;
//						while ((lng = (LineNumberTag) u.getTag("LineNumberTag")) == null) {
//							u = units.getSuccOf(u);
//						}
						if((lng = (LineNumberTag) u.getTag("LineNumberTag")) == null)
							continue;
						
						int ln = lng.getLineNumber();
						stmtStatistic.setStmt(className, ln, blkStatistic.getProperty(className, methodName, idx));
					}
				}
			}
			
		}
		
		

	}
	
	public void toStmtXML(File f){
		Element xmlRoot = new Element("statement_coverage_metrics");
		Document xmlDoc = new Document(xmlRoot);
		xmlRoot.setAttribute("trueRuns", String.valueOf(blkStatistic.totaltf.getTruTimes()));
		xmlRoot.setAttribute("falseRuns", String.valueOf(blkStatistic.totaltf.getFlsTimes()));
		long totalExecuted = 0;
		for(String klassName : stmtStatistic.getKlassNames()){
			Element xmlKlass = new Element("class");
			xmlKlass.setAttribute("name", klassName);
			for(Integer ln : stmtStatistic.getLine(klassName)){
				Element xmlLine = new Element("statement");
				xmlLine.setAttribute("line", ln.toString());
				StatisticProperty sp = stmtStatistic.getProperty(klassName, ln);
				if(sp==null){
//					System.out.println(klassName+ " " +ln);
					throw new IllegalArgumentException("StmtStatistic toXML StatisticProperty must not be null");
				}else{
					xmlLine.setAttribute("trueHits",
							String.valueOf(sp.tfs.getTruTimes()));
					xmlLine.setAttribute("falseHits",
							String.valueOf(sp.tfs.getFlsTimes()));
					xmlKlass.addContent(xmlLine);
				}
				totalExecuted++;
			}
			xmlRoot.addContent(xmlKlass);
		}
		xmlRoot.setAttribute("totalExecutedLines", String.valueOf(totalExecuted));

		try {
			new XMLOutputter().output(xmlDoc, new FileOutputStream(f));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void rank() {
		for(String klassName : stmtStatistic.getKlassNames()) {
			for(Integer ln : stmtStatistic.getLine(klassName)) {
				StatisticProperty sp = stmtStatistic.getProperty(klassName, ln);
				stmtResult.add(klassName, ln, sp.label);
			}
		}
		stmtResult.rank();
		Global.v().result = stmtResult;
	}
	

	
	@Override
	public void prepaerProfile(File file, List<String> args) {
		StatisticProvider p = new BlkStatisticProvider();
		try {
			//getProfiles and merge merge to blkStatistic
			//blkStatistic = (BlkStatistic) p.provideData(file, args);
			blkStatistic.merge((BlkStatistic) p.provideData(file, args));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void toXML(File f) {
		stmtResult.toXML(f);
	}

}
